package ext.render;

import context.ContextThread;
import context.environ.Window;
import context.render.framebuffer.FrameBuffer;
import context.render.light.Light;
import context.render.shader.Shader;
import context.render.state.StateGPU;
import context.render.tiu.GroupTIU;
import context.render.tiu.sampler.Sampler;
import context.render.vertexdata.VertexData;
import java.util.LinkedList;
import math.vec2i;
import math.vec3;

public class Renderer extends ContextThread
{
    
    public StateGPU mState;
    
    public GroupTIU mTIUs;
    
    private FrameBuffer mGBuffer;
    private FrameBuffer mAccumBuffer;
    
    public Shader mShadGBuff;
    public Shader mShadAccum;
    private Shader mShadDisplay;
    
    private LinkedList<VertexData> mQueuedGeom;
    
    private LinkedList<Light[]> mQueuedLights;
    
    
    public Renderer()
    {
        this.mState = new StateGPU();
        
        this.mTIUs = new GroupTIU();
        
        this.mGBuffer = new FrameBuffer(Window.get().mSize);
        this.mGBuffer.attachTexture();
        this.mGBuffer.attachTexture();
        this.mGBuffer.attachDepth();
        this.mGBuffer.verify();
        
        this.mAccumBuffer = new FrameBuffer(Window.get().mSize);
        this.mAccumBuffer.attachTexture();
        this.mAccumBuffer.verify();
        
        this.mShadGBuff = new Shader("gbuffer", "gbuffer");
        this.mShadAccum = new Shader("accum", "accum");
        this.mShadDisplay = new Shader("display", "display");
        
        this.mQueuedGeom = new LinkedList<>();
        this.mQueuedLights = new LinkedList<>();
    }
    
    
    public void queueGeom(VertexData aVertexData)
    {
        this.mQueuedGeom.addLast(aVertexData);
    }
    
    public void queueLights(Light[] aLights)
    {
        int aInd = 0;
        int nGroups = aLights.length / 4;
        int nRemain = aLights.length % 4;
        for (int iGrp = 0; iGrp < nGroups; iGrp++)
        {
            Light[] lGroup = new Light[4];
            for (int i = 0; i < lGroup.length; i++)
            {
                lGroup[i] = aLights[aInd++];
            }
            this.mQueuedLights.addLast(lGroup);
        }
        if (nRemain > 0)
        {
            Light[] lGroup = new Light[nRemain];
            for (int i = 0; i < lGroup.length; i++)
            {
                lGroup[i] = aLights[aInd++];
            }
            this.mQueuedLights.addLast(lGroup);
        }
    }
    
    /** Inherited from ContextThread. Called by Context in the main loop. */
    public void onTick()
    {
        // Do an error check every frame:
        this.mState.errorCheckGL();
        
        // RENDER PASS: WRITE TO GBUFFER ---------------------------------------------- //
        this.mState.frameBuffer(this.mGBuffer);
        this.mState.viewport(vec2i.ZERO, this.mGBuffer.mSize);
        this.mState.depthTest(true);
        this.mState.additiveColorBlend(false);
        this.mState.cullFace(false, true);
        this.mState.clearColor(vec3.ZERO, 0.0);
        this.mState.clearBuffers();
        
        this.mState.shader(this.mShadGBuff);
        
        this.mShadGBuff.flushUniforms();
        
        while (!this.mQueuedGeom.isEmpty())
        {
            VertexData vdata = this.mQueuedGeom.removeFirst();
            vdata.send();
        }
        
        // RENDER PASS: WRITE TO ACCUM ------------------------------------------------ //
        this.mState.frameBuffer(this.mAccumBuffer);
        this.mState.viewport(vec2i.ZERO, this.mAccumBuffer.mSize);
        this.mState.depthTest(false);
        this.mState.additiveColorBlend(true);
        this.mState.cullFace(false, false);
        this.mState.clearColor(vec3.ZERO, 0.0);
        this.mState.clearBuffers();
        
        this.mState.shader(this.mShadAccum);
        
        this.mShadAccum.uniform2i("V2_VIEWPORT_DIM", Window.get().mSize);
        
        this.mTIUs.get(1).bind(Sampler.NEAREST);
        this.mTIUs.get(1).bind(this.mGBuffer.texture(0));
        this.mShadAccum.uniform1i("T2_COMPOS_NORM_DEPTH", 1);
        
        this.mTIUs.get(2).bind(Sampler.NEAREST);
        this.mTIUs.get(2).bind(this.mGBuffer.texture(1));
        this.mShadAccum.uniform1i("T2_COMPOS_ALBEDO", 2);
        
        while (!this.mQueuedLights.isEmpty())
        {
            Light[] lGroup = this.mQueuedLights.removeFirst();
            this.mShadAccum.uniform1i("I1_NUM_LIGHTS", lGroup.length); // max = 4.
            for (int i = 0; i < lGroup.length; i++)
            {
                lGroup[i].bind(this.mShadAccum, i);
            }
            
            this.mShadAccum.flushUniforms();
            
            VertexData.QUAD_FULL.send();
        }
        
        // RENDER PASS: WRITE TO SCREEN ----------------------------------------------- //
        this.mState.frameBuffer(null);
        this.mState.viewport(vec2i.ZERO, Window.get().mSize);
        this.mState.depthTest(false);
        this.mState.additiveColorBlend(false);
        this.mState.cullFace(false, false);
        this.mState.clearColor(vec3.ZERO, 0.0);
        this.mState.clearBuffers();
        
        this.mState.shader(this.mShadDisplay);
        
        this.mShadDisplay.uniform2i("V2_VIEWPORT_DIM", Window.get().mSize);
        
        this.mTIUs.get(3).bind(Sampler.NEAREST);
        this.mTIUs.get(3).bind(this.mAccumBuffer.texture(0));
        this.mShadDisplay.uniform1i("T2_INPUT", 3);
        
        this.mShadDisplay.flushUniforms();
        
        VertexData.QUAD_FULL.send();
    }
    
    /** Inherited from ContextThread. Called by Context as it terminates. */
    public void onEnd()
    {
        this.mGBuffer.onEnd();
        this.mAccumBuffer.onEnd();
        this.mShadDisplay.onEnd();
    }
    
}
